Граф G = (V, E)
называется двудольным, если множество его вершин V можно разбить на два
подмножества A и B так, что любое ребро из E соединяет вершину из A с вершиной
из B.
Паросочетанием P
называется любое подмножество E, такое что никакие два ребра из этого множетва
не имеют общих вершин.
Максимальное
паросочетание – это паросочетание, содержащее максимальное количество рёбер.
Найдите
максимальное паросочетание в заданном двудольном графе.
Вход. В первой строке заданы три числа n, m и k (1 ≤ n, m ≤ 100, 1 ≤ k ≤ 10000), где n – число вершин
в множестве A, m – число вершин в множестве B, а k – количество рёбер в графе. Каждая из следующих k строк содержит по два числа ui и vi, обозначающих, что вершина ui из множества A соединена с вершиной vi из множества B. Вершины во
множествах A и B нумеруются независимо, начиная с единицы. Все входные числа
целые.
Выход. В первой строке
выведите количество l рёбер в
максимальном паросочетании. Далее выведите l
строк, каждая
из которых содержит два числа: a и b, где a – вершина
из множества A, а b – соответствующая ей вершина из
множества B в паросочетании. Взятые рёбра должны образовывать максимальное
паросочетание.
Пример
входа |
Пример
выхода |
2 3 4 1 1 1 2 2 2
2 3 |
2 1 1 2 3 |
максимальное
паросочетание
Классическая
задача о максимальном паросочетании, которую можно либо свести к потокам на
графе, либо решить при помощи алгоритма дополняющего пути.
Пример
Входной граф и
максимальное паросочетание имеет вид:
Реализация алгоритма
Рассмотрим
алгоритм дополняющего пути без оптимизации. Граф храним в списке смежности g.
Для отмечания в поиске в глубину пройденных вершин используем массив used.
Для хранения текущего паросочетания используем массив mt.
#define MAX 110
vector<vector<int>
> g;
vector<int> used, mt;
Функция dfs
ищет дополняющий путь из вершины v
поиском в глубину и возвращает 1, если такой путь будет найден. В случае
нахождения увеличивающей цепи производится чередование паросочетания вдоль нее.
int dfs(int v)
{
if (used[v]) return
0;
used[v] = 1;
for (int to : g[v])
if (mt[to] == -1 || dfs(mt[to]))
{
mt[to] = v;
return 1;
}
return 0;
}
Функция AugmentingPath находит максимальное паросочетание.
void AugmentingPath(void)
{
Изначально текущее паросочетание пусто.
mt.assign (m + 1,
-1);
Запускаем поиск дополняющего пути из каждой вершины левой
доли. Предварительно обнуляем массив посещенных вершин used.
for (int i = 1; i
<= n; i++)
{
used.assign(n + 1,
0);
dfs(i);
}
}
Основная часть программы. Читаем входные данные.
scanf("%d %d %d",&n,&m,&k);
g.resize(n + 1);
for(i = 0; i < k; i++)
{
scanf("%d %d",&a,&b);
g[a].push_back(b);
}
Запускаем алгоритм поиска максимального паросочетания.
AugmentingPath();
Размер максимального паросочетания вычисляем в переменной flow.
flow = 0;
for (i = 1; i <= m; i++)
if (mt[i] != -1) flow++;
Выводим величину максимального паросочетания.
printf("%d\n",flow);
Выводим само максимальное паросочетание.
for (i = 1; i <= m; i++)
if (mt[i] != -1) printf("%d
%d\n",mt[i],i);
Реализация с оптимизацией
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace
std;
vector<vector<int> > g;
vector<int> used, mt, par;
int i, j, ptr;
int n, m, k, a, b, flow;
int dfs(int
v)
{
if (used[v]) return 0;
used[v] = 1;
for (int i = 0; i < g[v].size(); i++)
{
int to =
g[v][i];
if (mt[to]
== -1 || dfs(mt[to]))
{
mt[to] = v;
par[v] = to;
return 1;
}
}
return 0;
}
void AugmentingPath(void)
{
int i, run;
mt.assign (m+1, -1);
par.assign (n+1, -1);
for (run = 1;
run; )
{
run = 0; used.assign(n+1, 0);
for (i = 1;
i <= n; i++)
if
((par[i] == -1) && dfs(i)) run = 1;
}
}
int main(void)
{
scanf("%d %d
%d",&n,&m,&k);
g.resize(n+1);
for(i = 0; i
< k; i++)
{
scanf("%d
%d",&a,&b);
g[a].push_back(b);
}
AugmentingPath();
for (flow =
0, i = 1; i <= m; i++)
if (mt[i]
!= -1) flow++;
printf("%d\n",flow);
for (i = 1; i
<= m; i++)
if (mt[i]
!= -1) printf("%d %d\n",mt[i],i);
return 0;
}